// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package com.cloud.hypervisor.vmware.util; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; import javax.xml.ws.BindingProvider; import javax.xml.ws.WebServiceException; import javax.xml.ws.handler.MessageContext; import org.apache.log4j.Logger; import com.vmware.vim25.DynamicProperty; import com.vmware.vim25.InvalidCollectorVersionFaultMsg; import com.vmware.vim25.InvalidPropertyFaultMsg; import com.vmware.vim25.LocalizedMethodFault; import com.vmware.vim25.ManagedObjectReference; import com.vmware.vim25.ObjectContent; import com.vmware.vim25.ObjectSpec; import com.vmware.vim25.ObjectUpdate; import com.vmware.vim25.ObjectUpdateKind; import com.vmware.vim25.PropertyChange; import com.vmware.vim25.PropertyChangeOp; import com.vmware.vim25.PropertyFilterSpec; import com.vmware.vim25.PropertyFilterUpdate; import com.vmware.vim25.PropertySpec; import com.vmware.vim25.RuntimeFaultFaultMsg; import com.vmware.vim25.SelectionSpec; import com.vmware.vim25.ServiceContent; import com.vmware.vim25.TaskInfoState; import com.vmware.vim25.TraversalSpec; import com.vmware.vim25.UpdateSet; import com.vmware.vim25.VimPortType; import com.vmware.vim25.VimService; import org.apache.cloudstack.utils.security.SSLUtils; import org.apache.cloudstack.utils.security.SecureSSLSocketFactory; /** * A wrapper class to handle Vmware vsphere connection and disconnection. * * DISCLAIMER: This code is partly copied from sample codes that come along with Vmware web service 5.1 SDK. * */ public class VmwareClient { private static final Logger s_logger = Logger.getLogger(VmwareClient.class); private static class TrustAllTrustManager implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager { @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException { return; } @Override public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException { return; } } static { try { trustAllHttpsCertificates(); HostnameVerifier hv = new HostnameVerifier() { @Override public boolean verify(String urlHostName, SSLSession session) { return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(hv); vimService = new VimService(); } catch (Exception e) { s_logger.info("[ignored]" + "failed to trust all certificates blindly: " + e.getLocalizedMessage()); } } private static void trustAllHttpsCertificates() throws Exception { // Create a trust manager that does not validate certificate chains: javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1]; javax.net.ssl.TrustManager tm = new TrustAllTrustManager(); trustAllCerts[0] = tm; javax.net.ssl.SSLContext sc = SSLUtils.getSSLContext(); javax.net.ssl.SSLSessionContext sslsc = sc.getServerSessionContext(); sslsc.setSessionTimeout(0); sc.init(null, trustAllCerts, null); javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(new SecureSSLSocketFactory(sc)); } private final ManagedObjectReference svcInstRef = new ManagedObjectReference(); private static VimService vimService; private VimPortType vimPort; private String serviceCookie; private final static String SVC_INST_NAME = "ServiceInstance"; private int vCenterSessionTimeout = 1200000; // Timeout in milliseconds private boolean isConnected = false; public VmwareClient(String name) { } /** * Establishes session with the virtual center server. * * @throws Exception * the exception */ public void connect(String url, String userName, String password) throws Exception { svcInstRef.setType(SVC_INST_NAME); svcInstRef.setValue(SVC_INST_NAME); vimPort = vimService.getVimPort(); Map<String, Object> ctxt = ((BindingProvider)vimPort).getRequestContext(); ctxt.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, url); ctxt.put(BindingProvider.SESSION_MAINTAIN_PROPERTY, true); ctxt.put("com.sun.xml.internal.ws.request.timeout", vCenterSessionTimeout); ctxt.put("com.sun.xml.internal.ws.connect.timeout", vCenterSessionTimeout); ServiceContent serviceContent = vimPort.retrieveServiceContent(svcInstRef); // Extract a cookie. See vmware sample program com.vmware.httpfileaccess.GetVMFiles @SuppressWarnings("unchecked") Map<String, List<String>> headers = (Map<String, List<String>>)((BindingProvider)vimPort).getResponseContext().get(MessageContext.HTTP_RESPONSE_HEADERS); List<String> cookies = headers.get("Set-cookie"); vimPort.login(serviceContent.getSessionManager(), userName, password, null); if (cookies == null) { // Get the cookie from the response header. See vmware sample program com.vmware.httpfileaccess.GetVMFiles @SuppressWarnings("unchecked") Map<String, List<String>> responseHeaders = (Map<String, List<String>>)((BindingProvider)vimPort).getResponseContext().get(MessageContext.HTTP_RESPONSE_HEADERS); cookies = responseHeaders.get("Set-cookie"); if (cookies == null) { String msg = "Login successful, but failed to get server cookies from url :[" + url + "]"; s_logger.error(msg); throw new Exception(msg); } } String cookieValue = cookies.get(0); StringTokenizer tokenizer = new StringTokenizer(cookieValue, ";"); cookieValue = tokenizer.nextToken(); String pathData = "$" + tokenizer.nextToken(); serviceCookie = "$Version=\"1\"; " + cookieValue + "; " + pathData; isConnected = true; } /** * Disconnects the user session. * * @throws Exception */ public void disconnect() throws Exception { if (isConnected) { vimPort.logout(getServiceContent().getSessionManager()); } isConnected = false; } /** * @return Service instance */ public VimPortType getService() { return vimPort; } /** * @return Service instance content */ public ServiceContent getServiceContent() { try { return vimPort.retrieveServiceContent(svcInstRef); } catch (RuntimeFaultFaultMsg e) { } return null; } /** * @return cookie used in service connection */ public String getServiceCookie() { return serviceCookie; } /** * @return Service property collector */ public ManagedObjectReference getPropCol() { return getServiceContent().getPropertyCollector(); } /** * @return Root folder */ public ManagedObjectReference getRootFolder() { return getServiceContent().getRootFolder(); } public boolean validate() { // // There is no official API to validate an open vCenter API session. This is hacking way to tell if // an open vCenter API session is still valid for making calls. // // It will give false result if there really does not exist data-center in the inventory, however, I consider // this really is not possible in production deployment // // Create PropertySpecs PropertySpec pSpec = new PropertySpec(); pSpec.setType("Datacenter"); pSpec.setAll(false); pSpec.getPathSet().add("name"); ObjectSpec oSpec = new ObjectSpec(); oSpec.setObj(getRootFolder()); oSpec.setSkip(false); oSpec.getSelectSet().addAll(constructCompleteTraversalSpec()); PropertyFilterSpec spec = new PropertyFilterSpec(); spec.getPropSet().add(pSpec); spec.getObjectSet().add(oSpec); List<PropertyFilterSpec> specArr = new ArrayList<PropertyFilterSpec>(); specArr.add(spec); try { List<ObjectContent> ocary = vimPort.retrieveProperties(getPropCol(), specArr); if (ocary != null && ocary.size() > 0) return true; } catch (Exception e) { return false; } return false; } /** * Get the property value of a managed object. * * @param mor * managed object reference * @param propertyName * property name. * @return property value. * @throws Exception * in case of error. */ @SuppressWarnings("unchecked") public <T> T getDynamicProperty(ManagedObjectReference mor, String propertyName) throws Exception { List<String> props = new ArrayList<String>(); props.add(propertyName); List<ObjectContent> objContent = retrieveMoRefProperties(mor, props); Object propertyValue = null; if (objContent != null && objContent.size() > 0) { List<DynamicProperty> dynamicProperty = objContent.get(0).getPropSet(); if (dynamicProperty != null && dynamicProperty.size() > 0) { DynamicProperty dp = dynamicProperty.get(0); propertyValue = dp.getVal(); /* * If object is ArrayOfXXX object, then get the XXX[] by * invoking getXXX() on the object. * For Ex: * ArrayOfManagedObjectReference.getManagedObjectReference() * returns ManagedObjectReference[] array. */ Class dpCls = propertyValue.getClass(); String dynamicPropertyName = dpCls.getName(); if (dynamicPropertyName.indexOf("ArrayOf") != -1) { String methodName = "get" + dynamicPropertyName.substring(dynamicPropertyName.indexOf("ArrayOf") + "ArrayOf".length(), dynamicPropertyName.length()); Method getMorMethod = dpCls.getDeclaredMethod(methodName, null); propertyValue = getMorMethod.invoke(propertyValue, (Object[])null); } } } return (T)propertyValue; } private List<ObjectContent> retrieveMoRefProperties(ManagedObjectReference mObj, List<String> props) throws Exception { PropertySpec pSpec = new PropertySpec(); pSpec.setAll(false); pSpec.setType(mObj.getType()); pSpec.getPathSet().addAll(props); ObjectSpec oSpec = new ObjectSpec(); oSpec.setObj(mObj); oSpec.setSkip(false); PropertyFilterSpec spec = new PropertyFilterSpec(); spec.getPropSet().add(pSpec); spec.getObjectSet().add(oSpec); List<PropertyFilterSpec> specArr = new ArrayList<PropertyFilterSpec>(); specArr.add(spec); return vimPort.retrieveProperties(getPropCol(), specArr); } /** * This method returns a boolean value specifying whether the Task is * succeeded or failed. * * @param task * ManagedObjectReference representing the Task. * * @return boolean value representing the Task result. * @throws InvalidCollectorVersionFaultMsg * @throws RuntimeFaultFaultMsg * @throws InvalidPropertyFaultMsg */ public boolean waitForTask(ManagedObjectReference task) throws InvalidPropertyFaultMsg, RuntimeFaultFaultMsg, InvalidCollectorVersionFaultMsg, Exception { boolean retVal = false; try { // info has a property - state for state of the task Object[] result = waitForValues(task, new String[] { "info.state", "info.error" }, new String[] { "state" }, new Object[][] { new Object[] { TaskInfoState.SUCCESS, TaskInfoState.ERROR } }); if (result[0].equals(TaskInfoState.SUCCESS)) { retVal = true; } if (result[1] instanceof LocalizedMethodFault) { throw new RuntimeException(((LocalizedMethodFault) result[1]).getLocalizedMessage()); } } catch(WebServiceException we) { s_logger.debug("Cancelling vCenter task because task failed with " + we.getLocalizedMessage()); getService().cancelTask(task); throw new RuntimeException("vCenter task failed due to " + we.getLocalizedMessage()); } return retVal; } /** * Handle Updates for a single object. waits till expected values of * properties to check are reached Destroys the ObjectFilter when done. * * @param objmor * MOR of the Object to wait for * @param filterProps * Properties list to filter * @param endWaitProps * Properties list to check for expected values these be * properties of a property in the filter properties list * @param expectedVals * values for properties to end the wait * @return true indicating expected values were met, and false otherwise * @throws RuntimeFaultFaultMsg * @throws InvalidPropertyFaultMsg * @throws InvalidCollectorVersionFaultMsg */ private Object[] waitForValues(ManagedObjectReference objmor, String[] filterProps, String[] endWaitProps, Object[][] expectedVals) throws InvalidPropertyFaultMsg, RuntimeFaultFaultMsg, InvalidCollectorVersionFaultMsg { // version string is initially null String version = ""; Object[] endVals = new Object[endWaitProps.length]; Object[] filterVals = new Object[filterProps.length]; PropertyFilterSpec spec = new PropertyFilterSpec(); ObjectSpec oSpec = new ObjectSpec(); oSpec.setObj(objmor); oSpec.setSkip(Boolean.FALSE); spec.getObjectSet().add(oSpec); PropertySpec pSpec = new PropertySpec(); pSpec.getPathSet().addAll(Arrays.asList(filterProps)); pSpec.setType(objmor.getType()); spec.getPropSet().add(pSpec); ManagedObjectReference propertyCollector = getPropCol(); ManagedObjectReference filterSpecRef = vimPort.createFilter(propertyCollector, spec, true); boolean reached = false; UpdateSet updateset = null; List<PropertyFilterUpdate> filtupary = null; List<ObjectUpdate> objupary = null; List<PropertyChange> propchgary = null; while (!reached) { updateset = vimPort.waitForUpdates(propertyCollector, version); if (updateset == null || updateset.getFilterSet() == null) { continue; } version = updateset.getVersion(); // Make this code more general purpose when PropCol changes later. filtupary = updateset.getFilterSet(); for (PropertyFilterUpdate filtup : filtupary) { objupary = filtup.getObjectSet(); for (ObjectUpdate objup : objupary) { // TODO: Handle all "kind"s of updates. if (objup.getKind() == ObjectUpdateKind.MODIFY || objup.getKind() == ObjectUpdateKind.ENTER || objup.getKind() == ObjectUpdateKind.LEAVE) { propchgary = objup.getChangeSet(); for (PropertyChange propchg : propchgary) { updateValues(endWaitProps, endVals, propchg); updateValues(filterProps, filterVals, propchg); } } } } Object expctdval = null; // Check if the expected values have been reached and exit the loop // if done. // Also exit the WaitForUpdates loop if this is the case. for (int chgi = 0; chgi < endVals.length && !reached; chgi++) { for (int vali = 0; vali < expectedVals[chgi].length && !reached; vali++) { expctdval = expectedVals[chgi][vali]; reached = expctdval.equals(endVals[chgi]) || reached; } } } // Destroy the filter when we are done. vimPort.destroyPropertyFilter(filterSpecRef); return filterVals; } private void updateValues(String[] props, Object[] vals, PropertyChange propchg) { for (int findi = 0; findi < props.length; findi++) { if (propchg.getName().lastIndexOf(props[findi]) >= 0) { if (propchg.getOp() == PropertyChangeOp.REMOVE) { vals[findi] = ""; } else { vals[findi] = propchg.getVal(); } } } } private SelectionSpec getSelectionSpec(String name) { SelectionSpec genericSpec = new SelectionSpec(); genericSpec.setName(name); return genericSpec; } /* * @return An array of SelectionSpec covering VM, Host, Resource pool, * Cluster Compute Resource and Datastore. */ private List<SelectionSpec> constructCompleteTraversalSpec() { // ResourcePools to VM: RP -> VM TraversalSpec rpToVm = new TraversalSpec(); rpToVm.setName("rpToVm"); rpToVm.setType("ResourcePool"); rpToVm.setPath("vm"); rpToVm.setSkip(Boolean.FALSE); // VirtualApp to VM: vApp -> VM TraversalSpec vAppToVM = new TraversalSpec(); vAppToVM.setName("vAppToVM"); vAppToVM.setType("VirtualApp"); vAppToVM.setPath("vm"); // Host to VM: HostSystem -> VM TraversalSpec hToVm = new TraversalSpec(); hToVm.setType("HostSystem"); hToVm.setPath("vm"); hToVm.setName("hToVm"); hToVm.getSelectSet().add(getSelectionSpec("VisitFolders")); hToVm.setSkip(Boolean.FALSE); // DataCenter to DataStore: DC -> DS TraversalSpec dcToDs = new TraversalSpec(); dcToDs.setType("Datacenter"); dcToDs.setPath("datastore"); dcToDs.setName("dcToDs"); dcToDs.setSkip(Boolean.FALSE); // Recurse through all ResourcePools TraversalSpec rpToRp = new TraversalSpec(); rpToRp.setType("ResourcePool"); rpToRp.setPath("resourcePool"); rpToRp.setSkip(Boolean.FALSE); rpToRp.setName("rpToRp"); rpToRp.getSelectSet().add(getSelectionSpec("rpToRp")); TraversalSpec crToRp = new TraversalSpec(); crToRp.setType("ComputeResource"); crToRp.setPath("resourcePool"); crToRp.setSkip(Boolean.FALSE); crToRp.setName("crToRp"); crToRp.getSelectSet().add(getSelectionSpec("rpToRp")); TraversalSpec crToH = new TraversalSpec(); crToH.setSkip(Boolean.FALSE); crToH.setType("ComputeResource"); crToH.setPath("host"); crToH.setName("crToH"); TraversalSpec dcToHf = new TraversalSpec(); dcToHf.setSkip(Boolean.FALSE); dcToHf.setType("Datacenter"); dcToHf.setPath("hostFolder"); dcToHf.setName("dcToHf"); dcToHf.getSelectSet().add(getSelectionSpec("VisitFolders")); TraversalSpec vAppToRp = new TraversalSpec(); vAppToRp.setName("vAppToRp"); vAppToRp.setType("VirtualApp"); vAppToRp.setPath("resourcePool"); vAppToRp.getSelectSet().add(getSelectionSpec("rpToRp")); TraversalSpec dcToVmf = new TraversalSpec(); dcToVmf.setType("Datacenter"); dcToVmf.setSkip(Boolean.FALSE); dcToVmf.setPath("vmFolder"); dcToVmf.setName("dcToVmf"); dcToVmf.getSelectSet().add(getSelectionSpec("VisitFolders")); // For Folder -> Folder recursion TraversalSpec visitFolders = new TraversalSpec(); visitFolders.setType("Folder"); visitFolders.setPath("childEntity"); visitFolders.setSkip(Boolean.FALSE); visitFolders.setName("VisitFolders"); List<SelectionSpec> sspecarrvf = new ArrayList<SelectionSpec>(); sspecarrvf.add(getSelectionSpec("crToRp")); sspecarrvf.add(getSelectionSpec("crToH")); sspecarrvf.add(getSelectionSpec("dcToVmf")); sspecarrvf.add(getSelectionSpec("dcToHf")); sspecarrvf.add(getSelectionSpec("vAppToRp")); sspecarrvf.add(getSelectionSpec("vAppToVM")); sspecarrvf.add(getSelectionSpec("dcToDs")); sspecarrvf.add(getSelectionSpec("hToVm")); sspecarrvf.add(getSelectionSpec("rpToVm")); sspecarrvf.add(getSelectionSpec("VisitFolders")); visitFolders.getSelectSet().addAll(sspecarrvf); List<SelectionSpec> resultspec = new ArrayList<SelectionSpec>(); resultspec.add(visitFolders); resultspec.add(crToRp); resultspec.add(crToH); resultspec.add(dcToVmf); resultspec.add(dcToHf); resultspec.add(vAppToRp); resultspec.add(vAppToVM); resultspec.add(dcToDs); resultspec.add(hToVm); resultspec.add(rpToVm); resultspec.add(rpToRp); return resultspec; } /** * Get the ManagedObjectReference for an item under the * specified root folder that has the type and name specified. * * @param root a root folder if available, or null for default * @param type type of the managed object * @param name name to match * * @return First ManagedObjectReference of the type / name pair found */ public ManagedObjectReference getDecendentMoRef(ManagedObjectReference root, String type, String name) throws Exception { if (name == null || name.length() == 0) { return null; } try { // Create PropertySpecs PropertySpec pSpec = new PropertySpec(); pSpec.setType(type); pSpec.setAll(false); pSpec.getPathSet().add("name"); ObjectSpec oSpec = new ObjectSpec(); oSpec.setObj(root); oSpec.setSkip(false); oSpec.getSelectSet().addAll(constructCompleteTraversalSpec()); PropertyFilterSpec spec = new PropertyFilterSpec(); spec.getPropSet().add(pSpec); spec.getObjectSet().add(oSpec); List<PropertyFilterSpec> specArr = new ArrayList<PropertyFilterSpec>(); specArr.add(spec); ManagedObjectReference propCollector = getPropCol(); List<ObjectContent> ocary = vimPort.retrieveProperties(propCollector, specArr); if (ocary == null || ocary.size() == 0) { return null; } // filter through retrieved objects to get the first match. for (ObjectContent oc : ocary) { ManagedObjectReference mor = oc.getObj(); List<DynamicProperty> propary = oc.getPropSet(); if (type == null || type.equals(mor.getType())) { if (propary.size() > 0) { String propval = (String)propary.get(0).getVal(); if (propval != null && name.equalsIgnoreCase(propval)) return mor; } } } } catch (InvalidPropertyFaultMsg invalidPropertyException) { s_logger.debug("Failed to get Vmware ManagedObjectReference for name: " + name + " and type: " + type + " due to " + invalidPropertyException.getMessage()); throw invalidPropertyException; } catch (RuntimeFaultFaultMsg runtimeFaultException) { s_logger.debug("Failed to get Vmware ManagedObjectReference for name: " + name + " and type: " + type + " due to " + runtimeFaultException.getMessage()); throw runtimeFaultException; } return null; } /** * Get a MORef from the property returned. * * @param objMor Object to get a reference property from * @param propName name of the property that is the MORef * @return the ManagedObjectReference for that property. */ public ManagedObjectReference getMoRefProp(ManagedObjectReference objMor, String propName) throws Exception { Object props = getDynamicProperty(objMor, propName); ManagedObjectReference propmor = null; if (!props.getClass().isArray()) { propmor = (ManagedObjectReference)props; } return propmor; } public void setVcenterSessionTimeout(int vCenterSessionTimeout) { this.vCenterSessionTimeout = vCenterSessionTimeout; } public int getVcenterSessionTimeout() { return vCenterSessionTimeout; } }